package gitee.derumin.railway_accelerator.mixin;

import gitee.derumin.lib_for_derumin.helpers.calculators.MathTools;
import gitee.derumin.lib_for_derumin.helpers.calculators.NotSoPrecisedPlaneCalculator;
import gitee.derumin.railway_accelerator.things.MinecartPlus;
import gitee.derumin.railway_accelerator.things.MinecartTrain;
import net.minecraft.block.*;
import net.minecraft.block.enums.RailShape;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.data.DataTracker;
import net.minecraft.entity.passive.IronGolemEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.vehicle.AbstractMinecartEntity;
import net.minecraft.entity.vehicle.VehicleEntity;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.ItemStack;
import net.minecraft.item.MinecartItem;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.predicate.entity.EntityPredicates;
import net.minecraft.registry.tag.BlockTags;
import net.minecraft.registry.tag.FluidTags;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundEvents;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraft.world.event.GameEvent;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import java.util.List;
import java.util.Optional;

@Mixin(AbstractMinecartEntity.class)
public abstract class AbstractMinecartEntityMixin extends VehicleEntity implements MinecartPlus {
    @Shadow private boolean onRail;
    @Shadow public abstract void onActivatorRail(int x, int y, int z, boolean powered);
    @Shadow public abstract AbstractMinecartEntity.Type getMinecartType();
    @Shadow public abstract void pushAwayFrom(Entity entity);

    @Shadow protected abstract void initDataTracker(DataTracker.Builder builder);

    @Shadow protected abstract void readCustomDataFromNbt(NbtCompound nbt);

    @Unique private static final double SQRT1D2 = Math.sqrt(0.5d);
    @Unique private static final NotSoPrecisedPlaneCalculator.Segment AP = NotSoPrecisedPlaneCalculator.Segment.from(0.0d, 0.0625d, 0.9375d, 1.0d);
    @Unique private static final NotSoPrecisedPlaneCalculator.Segment AN = NotSoPrecisedPlaneCalculator.Segment.from(0.0625d, 1.0d, 1.0d, 0.0625d);
    @Unique private static final NotSoPrecisedPlaneCalculator.Segment APD = NotSoPrecisedPlaneCalculator.Segment.from(0.9375d, 0.0d, 1.0d, 0.0625d);
    @Unique private static final NotSoPrecisedPlaneCalculator.Segment AND = NotSoPrecisedPlaneCalculator.Segment.from(0.0d, 0.0625d, 0.0625d, 0.0d);
    @Unique private MinecartTrain train = null;
    @Unique private double push = 0d;
    @Unique private double resistance = 0d;
    @Unique private double lineV = 0d;
    @Unique private double rotateX = 0d;
    @Unique private double rotateZ = 1d;
    @Unique private YFixes yFixes = YFixes.KEEP;
    @Unique private boolean canOnRail = true;
    @Unique private boolean vc = false;
    @Unique private int beforeOnRail = 0;


    public AbstractMinecartEntityMixin(EntityType<?> entityType, World world) {
        super(entityType, world);
    }

    @Override
    public void railway_accelerator$setTrain(MinecartTrain train){
        this.train = train;
    }

    @Override
    public MinecartTrain railway_accelerator$getTrain(){
        return this.train;
    }

    @Override
    public double railway_accelerator$getMass(){
        switch (this.getMinecartType()){
            case RIDEABLE -> {
                return this.hasPassengers()? 2d : 1d;
            }
            case CHEST, HOPPER -> {
                try {
                    Inventory inventory = (Inventory)this;
                    double m = 6d;
                    for (int i = 0; i < inventory.size(); ++i) {
                        ItemStack itemStack = inventory.getStack(i);
                        if (itemStack.isEmpty()) continue;
                        m += (double) itemStack.getCount() /itemStack.getMaxCount();
                    }
                    return m *0.2d;
                }catch (Exception e){
                    return 1.2d;
                }
            }
            case FURNACE -> {
                return 5d;
            }
            case TNT, SPAWNER, COMMAND_BLOCK-> {
                return 1.5d;
            }
            case null, default -> {
                return 1d;
            }
        }
    }

    @Override
    public double railway_accelerator$getPush(){
        return this.push;
    }
    @Override
    public double railway_accelerator$getResistance(){
        return this.resistance;
    }

    @Override
    public ConnectionStatus railway_accelerator$follow(AbstractMinecartEntity goal, double delta, boolean firstMove){
        if (firstMove){
            this.vc = false;
        }
        Vec3d goalConnect = getConnectPoint(goal);
        double dx = goalConnect.x -this.getX();
        double dz = goalConnect.z -this.getZ();
        double l = Math.sqrt(dx*dx +dz*dz);
        double hw = this.getWidth()*0.5d +0.0625d;
        BlockPos pos = this.getBlockPos();
        BlockState state = this.getWorld().getBlockState(pos);
        BlockState stateDown = this.getWorld().getBlockState(pos.down());
        double deltaRemains = delta;
        while (deltaRemains > 1.0E-4){
            MoveData moveData;
            if (l > 1.0E-4) {
                moveData = this.getMoveDirection(state, stateDown, this.getPos(), dx /l, dz /l, (l -hw)/deltaRemains, this.getVelocity().y, deltaRemains);
            }else {
                moveData = this.getMoveDirection(state, stateDown, this.getPos(), this.rotateX, this.rotateZ, -hw/deltaRemains, this.getVelocity().y, deltaRemains);
            }
            this.yFixes = moveData.yFixes();
            this.setVelocity(moveData.getVelocity(this.lineV, this.getVelocity().y));
            this.simpleMove(moveData.move(), moveData.compressBoxY());
            deltaRemains -= moveData.deltaCost();
            dx = goalConnect.x -this.getX();
            dz = goalConnect.z -this.getZ();
            l = Math.sqrt(dx*dx +dz*dz);
            if (!this.getBlockPos().equals(pos)){
                pos = this.getBlockPos();
                state = this.getWorld().getBlockState(pos);
                stateDown = this.getWorld().getBlockState(pos.down());
            }
            this.onRail = checkPosOnRail(state, stateDown, this.getPos());
        }
        if (state.isOf(Blocks.ACTIVATOR_RAIL) && this.onRail){
            this.onActivatorRail(pos.getX(), pos.getY(), pos.getZ(), state.get(PoweredRailBlock.POWERED));
        }
        this.applyFluidEffect(delta);
        this.push = this.calcPush(state, stateDown);
        this.resistance = this.calcResistance(stateDown, this.lineV, false);
        this.railway_accelerator$updateHorizontalVector(this.getVelocity().x, this.getVelocity().z);
        this.prevYaw = this.getYaw();
        this.updateYaw(goalConnect.x -this.getX(), goalConnect.z -this.getZ());
        for (Entity entity : this.getWorld().getOtherEntities(this, this.getBoundingBox().expand(0.2f, 0.0, 0.2f))) {
            if (this.getMinecartType() == AbstractMinecartEntity.Type.RIDEABLE
                    && this.onRail
                    && this.lineV > 0.1d
                    && !this.hasPassengers()
                    && !entity.hasVehicle()
                    && !(entity instanceof PlayerEntity)
                    && !(entity instanceof IronGolemEntity)
                    && !(entity instanceof VehicleEntity)
                    && EntityPredicates.canBePushedBy(this).test(entity)){
                entity.startRiding(this);
            }
        }
        if (this.getY() +this.getHeight() +0.7d < goal.getY() || goal.getY() +goal.getHeight() +0.7d < this.getY()){
            return ConnectionStatus.VERTICAL_BROKEN;
        }else {
            Vec3d v = this.getVelocity();
            this.setVelocity(v.add(0d, delta*(goal.getVelocity().y -v.y), 0d));
        }
        if (l > hw +0.3d){
            return ConnectionStatus.HORIZONTAL_BROKEN;
        }
        return ConnectionStatus.CONNECT;
    }

    @Override
    public double railway_accelerator$moveOneStep(double tickRemains, boolean firstMove){
        if (firstMove){
            this.vc = false;
        }
        BlockPos pos = this.getBlockPos();
        BlockState state = this.getWorld().getBlockState(pos);
        BlockState stateDown = this.getWorld().getBlockState(pos.down());
        MoveData moveData = this.getMoveDirection(state, stateDown, this.getPos(), this.rotateX, this.rotateZ, this.lineV, this.getVelocity().y, tickRemains);
        this.yFixes = moveData.yFixes();
        this.setVelocity(moveData.getVelocity(moveData.lineV(), this.getVelocity().y));
        this.simpleMove(moveData.move(), moveData.compressBoxY());
        if (!this.getBlockPos().equals(pos)){
            pos = this.getBlockPos();
            state = this.getWorld().getBlockState(pos);
            stateDown = this.getWorld().getBlockState(pos.down());
        }
        this.onRail = checkPosOnRail(state, stateDown, this.getPos());
        if (state.isOf(Blocks.ACTIVATOR_RAIL) && this.onRail){
            this.onActivatorRail(pos.getX(), pos.getY(), pos.getZ(), state.get(PoweredRailBlock.POWERED));
        }
        this.applyFluidEffect(moveData.deltaCost());
        Vec3d vel = this.getVelocity();
        this.lineV = vel.horizontalLength();
        this.push = this.calcPush(state, stateDown);
        this.resistance = this.calcResistance(stateDown, this.lineV, true);
        this.railway_accelerator$updateHorizontalVector(vel.x, vel.z);
        this.prevYaw = this.getYaw();
        this.updateYaw(this.rotateX, this.rotateZ);
        for (Entity entity : this.getWorld().getOtherEntities(this, this.getBoundingBox().expand(0.2f, 0.0, 0.2f))) {
            if (this.getMinecartType() == AbstractMinecartEntity.Type.RIDEABLE
                    && this.onRail
                    && this.lineV > 0.1d
                    && !this.hasPassengers()
                    && !entity.hasVehicle()
                    && !(entity instanceof PlayerEntity)
                    && !(entity instanceof IronGolemEntity)
                    && !(entity instanceof VehicleEntity)
                    && EntityPredicates.canBePushedBy(this).test(entity)){
                entity.startRiding(this);
                continue;
            }
            this.train.crashOnEntity(entity, asMineCart());
        }
        return moveData.deltaCost();
    }

    @Override
    public void railway_accelerator$updateLineVelocity(double lineV){
        this.lineV = lineV;
        this.setVelocity(lineV *this.rotateX, this.yFixes.calcYSpeed(lineV, this.getVelocity().y), lineV *this.rotateZ);
    }

    @Override
    public double railway_accelerator$getLineVelocity(){
        return this.lineV;
    }

    @Override
    public void railway_accelerator$updateHorizontalVector(double x, double z){
        double l = x*x + z*z;
        if (l < 1.0E-8){
            return;
        }
        l = Math.sqrt(l);
        this.rotateX = x/l;
        this.rotateZ = z/l;
    }

    @Override
    public HorizontalVector railway_accelerator$getHorizontalVector(){
        return new HorizontalVector(this.rotateX, this.rotateZ);
    }

    @Override
    public void railway_accelerator$flip(){
        this.rotateX = -this.rotateX;
        this.rotateZ = -this.rotateZ;
        this.setVelocity(this.getVelocity().multiply(-1d, 1d, -1d));
    }

    @Override
    public void railway_accelerator$forceNotOnRail(){
        this.canOnRail = false;
        this.beforeOnRail = 20;
    }

    @Unique
    private Vec3d getConnectPoint(AbstractMinecartEntity goal){
        double width = goal.getWidth()*0.5d +0.0625d;
        HorizontalVector vector = goal.railway_accelerator$getHorizontalVector();
        if (vector.x()*(goal.getX() -this.getX()) + vector.z()*(goal.getZ() -this.getZ()) < 0d){
            goal.railway_accelerator$flip();
            return goal.getPos().add(width *vector.x(), 0d, width *vector.z());
        }else {
            return goal.getPos().add(-width *vector.x(), 0d, -width *vector.z());
        }
    }

    @Unique
    private AbstractMinecartEntity asMineCart(){
        return (AbstractMinecartEntity) ((Object)this);
    }

    @Unique
    private void updateYaw(double x, double z){
        if (!MathTools.between(x, -1.0E-4, 1.0E-4) || !MathTools.between(z, -1.0E-4, 1.0E-4)){
            this.setYaw((float) MathHelper.atan2(z, x));
        }
    }

    @Unique
    private double calcPush(BlockState state, BlockState stateDown){
        double p = 0d;
        double m = this.railway_accelerator$getMass() *this.getFinalGravity();
        if(this.onRail){
            RailShape railShape;
            if (AbstractRailBlock.isRail(state)) {
                railShape = state.get(((AbstractRailBlock) state.getBlock()).getShapeProperty());
            }else if (AbstractRailBlock.isRail(stateDown)){
                railShape = stateDown.get(((AbstractRailBlock) stateDown.getBlock()).getShapeProperty());
            }else {
                return p;
            }
            switch (railShape){
                case ASCENDING_EAST -> {
                    if (this.rotateX > 0d){
                        p -= m *SQRT1D2;
                    } else if (this.rotateX < 0d) {
                        p += m *SQRT1D2;
                    }
                }
                case ASCENDING_WEST -> {
                    if (this.rotateX > 0d){
                        p += m *SQRT1D2;
                    } else if (this.rotateX < 0d) {
                        p -= m *SQRT1D2;
                    }
                }
                case ASCENDING_NORTH -> {
                    if (this.rotateZ > 0d){
                        p += m *SQRT1D2;
                    } else if (this.rotateZ < 0d) {
                        p -= m *SQRT1D2;
                    }
                }
                case ASCENDING_SOUTH -> {
                    if (this.rotateZ > 0d){
                        p -= m *SQRT1D2;
                    } else if (this.rotateZ < 0d) {
                        p += m *SQRT1D2;
                    }
                }
                case null, default -> {}
            }
        }
        if (this.getFirstPassenger() instanceof PlayerEntity player){
            Vec3d vecP = player.getVelocity();
            double vp = vecP.horizontalLength();
            if (vp > 1.0E-4) {
                p += 0.04d *((vecP.x *this.rotateX + vecP.z *this.rotateZ)/vp);
            }
        }
        return p;
    }

    @Unique
    private double calcResistance(BlockState stateDown, double lineV, boolean isHead){
        double f;
        if (this.onRail){
            f = 0.01d;
        }else {
            if (this.isOnGround()){
                f = 1 -stateDown.getBlock().getSlipperiness();
                if (f > 0.01d){
                    f *= 0.5d;
                }
            }else {
                f = 0.005d;
            }
        }
        f *= this.railway_accelerator$getMass();
        f += (this.touchingWater? (this.submergedInWater? 0.05d : 0.03d) : 0.0d) *(isHead? 1.0d : 0.3d);
        return f *lineV *10d *SQRT1D2;
    }

    @Unique
    private void applyFluidEffect(double delta){
        this.fluidHeight.clear();
        if (this.updateMovementInFluid(FluidTags.WATER, 0.014 *delta)) {
            if (!this.touchingWater && !this.firstUpdate) {
                this.onSwimmingStart();
            }
            this.onLanding();
            this.touchingWater = true;
            this.extinguish();
            double eye = this.getEyeY();
            BlockPos blockPos = BlockPos.ofFloored(this.getX(), eye, this.getZ());
            this.submergedInWater = blockPos.getY() + this.getWorld().getFluidState(blockPos).getHeight(this.getWorld(), blockPos) > eye;
        } else {
            this.touchingWater = false;
            this.submergedInWater = false;
        }
        double d = this.getWorld().getDimension().ultrawarm() ? 0.007 : 0.0023333333333333335;
        if (this.updateMovementInFluid(FluidTags.LAVA, d *delta)){
            this.onLanding();
            if (!this.isFireImmune()){
                this.setOnFireFor((float)delta *15.0f);
                this.setDamageWobbleSide(-this.getDamageWobbleSide());
                this.setDamageWobbleTicks(10);
                this.scheduleVelocityUpdate();
                this.setDamageWobbleStrength(this.getDamageWobbleStrength() + (float) delta * 40.0f);
                this.emitGameEvent(GameEvent.ENTITY_DAMAGE, null);
                if (this.getDamageWobbleStrength() > 40.0f){
                    this.killAndDropSelf(this.getDamageSources().lava());
                }
                this.playSound(SoundEvents.ENTITY_GENERIC_BURN, 0.4f, 2.0f + this.random.nextFloat() * 0.4f);
            }
        }
    }

    @Unique
    private boolean checkPosOnRail(BlockState state, BlockState stateDown, Vec3d ps){
        if (!this.canOnRail){
            return false;
        }
        double xb = ps.x -Math.floor(ps.x);
        double yb = ps.y -Math.floor(ps.y);
        double zb = ps.z -Math.floor(ps.z);
        if (AbstractRailBlock.isRail(state)){
            RailShape railShape = state.get(((AbstractRailBlock)state.getBlock()).getShapeProperty());
            switch (railShape){
                case EAST_WEST, NORTH_SOUTH, NORTH_EAST, NORTH_WEST, SOUTH_EAST, SOUTH_WEST -> {
                    return (yb < 0.125d);
                }
                case ASCENDING_EAST -> {
                    return (xb -0.125d < yb && yb < xb +0.125d);
                }
                case ASCENDING_WEST -> {
                    return (0.875d -xb < yb && yb < 1.125d -xb);
                }
                case ASCENDING_NORTH -> {
                    return (0.875d -zb < yb && yb < 1.125d -zb);
                }
                case ASCENDING_SOUTH -> {
                    return (zb -0.125d < yb && yb < zb +0.125d);
                }
                case null, default -> {}
            }
        }else {
            if (AbstractRailBlock.isRail(stateDown)){
                RailShape railShape = stateDown.get(((AbstractRailBlock)stateDown.getBlock()).getShapeProperty());
                switch (railShape){
                    case ASCENDING_EAST -> {
                        return (yb < xb -0.875d);
                    }
                    case ASCENDING_WEST -> {
                        return (yb < 0.125d -xb);
                    }
                    case ASCENDING_NORTH -> {
                        return (yb < 0.125d -zb);
                    }
                    case ASCENDING_SOUTH -> {
                        return (yb < zb -0.875d);
                    }
                    case null, default -> {}
                }
            }
        }
        return false;
    }

    @Unique
    private void simpleMove(Vec3d movement, double compressBoxY){
        if (this.noClip) {
            this.setPosition(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z);
            return;
        }
        this.wasOnFire = this.isOnFire();
        this.getWorld().getProfiler().push("move");
        Box box = this.getBoundingBox();
        if (compressBoxY > 1.0E-4){
            box = box.shrink(0d, -compressBoxY, 0d);
        }
        Vec3d vec3d = movement.lengthSquared() == 0.0 ? movement : Entity.adjustMovementForCollisions(this, movement, box, this.getWorld(), List.of());
        this.fallDistance = 0f;
        this.setPosition(this.getX() + vec3d.x, this.getY() + vec3d.y, this.getZ() + vec3d.z);
        this.getWorld().getProfiler().pop();
        this.getWorld().getProfiler().push("rest");
        boolean bl = !MathHelper.approximatelyEquals(movement.x, vec3d.x);
        boolean bl2 = !MathHelper.approximatelyEquals(movement.z, vec3d.z);
        this.horizontalCollision = bl || bl2;
        this.verticalCollision = (movement.y != vec3d.y) || this.vc;
        this.vc = this.verticalCollision;
        this.groundCollision = this.verticalCollision && movement.y <= 0.0;
        this.collidedSoftly = this.horizontalCollision && this.hasCollidedSoftly(vec3d);
        this.setOnGround(this.groundCollision, vec3d);
        BlockPos blockPos = this.getPosWithYOffset(0.2f);
        BlockState blockState = this.getWorld().getBlockState(blockPos);
        if (this.isRemoved()) {
            this.getWorld().getProfiler().pop();
            return;
        }
        if (this.horizontalCollision) {
            Vec3d vec3d2 = this.getVelocity();
            this.setVelocity(bl ? 0.0 : vec3d2.x, vec3d2.y, bl2 ? 0.0 : vec3d2.z);
        }
        Block block = blockState.getBlock();
        if (movement.y != vec3d.y) {
            block.onEntityLand(this.getWorld(), this);
        }
        if (this.isOnGround()) {
            block.onSteppedOn(this.getWorld(), blockPos, blockState, this);
        }
        this.tryCheckBlockCollision();
        if (this.getWorld().getStatesInBoxIfLoaded(this.getBoundingBox().contract(1.0E-6)).noneMatch(state -> state.isIn(BlockTags.FIRE) || state.isOf(Blocks.LAVA))) {
            if (this.getFireTicks() <= 0) {
                this.setFireTicks(-1);
            }
            if (this.wasOnFire && (this.inPowderSnow || this.isWet())) {
                this.playExtinguishSound();
            }
        }
        if (this.isOnFire() && (this.inPowderSnow || this.isWet())) {
            this.setFireTicks(-1);
        }
        this.getWorld().getProfiler().pop();
    }

    @Unique
    private MoveData getMoveDirection(BlockState state, BlockState stateDown, Vec3d ps, double rotateX, double rotateZ, double lineV, double vyb, double deltaMax) {
        double xb = ps.x - Math.floor(ps.x);
        double yb = ps.y - Math.floor(ps.y);
        double zb = ps.z - Math.floor(ps.z);
        double vx = rotateX *lineV;
        double vy = vyb;
        double vz = rotateZ *lineV;
        lineV = Math.abs(lineV);
        boolean blx = MathTools.between(vx, -1.0E-4, 1.0E-4);
        boolean bly = MathTools.between(vy, -1.0E-4, 1.0E-4);
        boolean blz = MathTools.between(vz, -1.0E-4, 1.0E-4);
        if (lineV < 1.0E-4) {
            if (bly){
                return MoveData.noMove(rotateX, rotateZ, deltaMax);
            }
            return moveYOnly(state, stateDown, xb, yb, zb, rotateX, rotateZ, vyb, deltaMax);
        }
        double xf;
        double yf;
        double zf;
        double deltaCost;
        YFixes yFixes = YFixes.KEEP;
        RailShape railShape;
        if (this.canOnRail){
            if (AbstractRailBlock.isRail(state)){
                railShape = state.get(((AbstractRailBlock)state.getBlock()).getShapeProperty());
                final double vxNESW =(vx +vz > 0d? lineV : -lineV)*SQRT1D2;
                final double vxNWSE = (vx -vz > 0d? lineV : -lineV)*SQRT1D2;
                switch (railShape) {
                    case EAST_WEST -> {
                        if (yb < 0.125d) {
                            if (blx) {
                                xf = xb;
                                rotateX = this.random.nextBoolean()? -1d : 1d;
                                lineV *= 0.5d;
                                deltaCost = deltaMax;
                            } else {
                                xf = MathHelper.clamp(vx *deltaMax +xb, -0.005d, 1.005d);
                                rotateX = rotateX < 0d? -1d : 1d;
                                deltaCost = (xf -xb) /vx;
                            }
                            yf = vy *deltaCost +yb;
                            if (yf < 0.0625d) {
                                yf = 0.0625d;
                                yFixes = YFixes.FLAT;
                            } else if (yf > 1.005d) {
                                yf = 1.005d;
                                deltaCost = (yf -yb) /vy;
                                xf = vx *deltaMax +xb;
                            }
                            zf = 0.5d;
                            rotateZ = 0d;
                            return new MoveData(new Vec3d(xf -xb, yf -yb, zf -zb), rotateX, rotateZ, lineV, yFixes, deltaCost, 0d);
                        }
                    }
                    case ASCENDING_EAST -> {
                        if (xb -0.125d < yb && yb < xb +0.125d) {
                            if (blx) {
                                xf = xb;
                                rotateX = -1d;
                                lineV *= 0.5d;
                                deltaCost = deltaMax;
                            } else {
                                xf = MathHelper.clamp(vx *deltaMax +xb, -0.005d, 1.005d);
                                rotateX = rotateX < 0d? -1d : 1d;
                                deltaCost = (xf -xb) /vx;
                            }
                            yf = vy *deltaCost +yb;
                            if (yf < 0.0625d +xf) {
                                yf = 0.0625d +xf;
                                yFixes = rotateX < 0d? YFixes.DOWN : YFixes.UP;
                            } else if (yf > 1.005d) {
                                yf = 1.005d;
                                deltaCost = (yf -yb) /vy;
                                xf = vx *deltaCost +xb;
                            }
                            zf = 0.5d;
                            rotateZ = 0d;
                            return new MoveData(new Vec3d(xf -xb, yf -yb, zf -zb), rotateX, rotateZ, lineV, yFixes, deltaCost, xf > 0.5d? 1.0d -xf : 0d);
                        }
                    }
                    case ASCENDING_WEST -> {
                        if (0.875d -xb < yb && yb < 1.125d -xb) {
                            if (blx) {
                                xf = xb;
                                rotateX = 1d;
                                lineV *= 0.5d;
                                deltaCost = deltaMax;
                            } else {
                                xf = MathHelper.clamp(vx *deltaMax +xb, -0.005d, 1.005d);
                                rotateX = rotateX < 0d? -1d : 1d;
                                deltaCost = (xf -xb) /vx;
                            }
                            yf = vy *deltaCost +yb;
                            if (yf < 1.0625d -xf) {
                                yf = 1.0625d -xf;
                                yFixes = rotateX > 0d? YFixes.DOWN : YFixes.UP;
                            } else if (yf > 1.005d) {
                                yf = 1.005d;
                                deltaCost = (yf -yb)/vy;
                                xf = vx *deltaCost +xb;
                            }
                            zf = 0.5d;
                            rotateZ = 0d;
                            return new MoveData(new Vec3d(xf -xb, yf -yb, zf -zb), rotateX, rotateZ, lineV, yFixes, deltaCost, xf < 0.5d? xf : 0d);
                        }
                    }
                    case NORTH_SOUTH -> {
                        if (yb < 0.125d) {
                            if (blz) {
                                zf = zb;
                                rotateZ = this.random.nextBoolean()? -1d : 1d;
                                lineV *= 0.5d;
                                deltaCost = deltaMax;
                            } else {
                                zf = MathHelper.clamp(vz *deltaMax +zb, -0.005d, 1.005d);
                                rotateZ = rotateZ < 0d? -1d : 1d;
                                deltaCost = (zf -zb) /vz;
                            }
                            yf = vy *deltaCost +yb;
                            if (yf < 0.0625d) {
                                yf = 0.0625d;
                                yFixes = YFixes.FLAT;
                            } else if (yf > 1.005d) {
                                yf = 1.005d;
                                deltaCost = (yf -yb)/vy;
                                zf = vz *deltaCost +zb;
                            }
                            xf = 0.5d;
                            rotateX = 0d;
                            return new MoveData(new Vec3d(xf -xb, yf -yb, zf -zb), rotateX, rotateZ, lineV, yFixes, deltaCost, 0d);
                        }
                    }
                    case ASCENDING_NORTH -> {
                        if (0.875d -zb < yb && yb < 1.125d -zb) {
                            if (blz) {
                                zf = zb;
                                rotateZ = 1d;
                                lineV *= 0.5d;
                                deltaCost = deltaMax;
                            } else {
                                zf = MathHelper.clamp(vz *deltaMax +zb, -0.005d, 1.005d);
                                rotateZ = rotateZ < 0d? -1d : 1d;
                                deltaCost = (zf -zb)/ vz;
                            }
                            yf = vy *deltaCost +yb;
                            if (yf < 1.0625d -zf) {
                                yf = 1.0625d -zf;
                                yFixes = rotateZ > 0d? YFixes.DOWN : YFixes.UP;
                            } else if (yf > 1.005d) {
                                yf = 1.005d;
                                deltaCost = (yf -yb)/vy;
                                zf = vz *deltaCost +zb;
                            }
                            xf = 0.5d;
                            rotateX = 0d;
                            return new MoveData(new Vec3d(xf -xb, yf -yb, zf -zb), rotateX, rotateZ, lineV, yFixes, deltaCost, zf < 0.5d? zf : 0d);
                        }
                    }
                    case ASCENDING_SOUTH -> {
                        if (zb -0.125d < yb && yb < zb +0.125d) {
                            if (blz) {
                                zf = zb;
                                rotateZ = -1d;
                                lineV *= 0.5d;
                                deltaCost = deltaMax;
                            } else {
                                zf = MathHelper.clamp(vz *deltaMax +zb, -0.005d, 1.005d);
                                rotateZ = rotateZ < 0d? -1d : 1d;
                                deltaCost = (zf -zb) /vz;
                            }
                            yf = vy *deltaCost +yb;
                            if (yf < 0.0625d +zf) {
                                yf = 0.0625d +zf;
                                yFixes = rotateZ < 0d? YFixes.DOWN : YFixes.UP;
                            } else if (yf > 1.005d) {
                                yf = 1.005d;
                                deltaCost = (yf -yb)/vy;
                                zf = vz *deltaCost +zb;
                            }
                            xf = 0.5d;
                            rotateX = 0d;
                            return new MoveData(new Vec3d(xf -xb, yf -yb, zf -zb), rotateX, rotateZ, lineV, yFixes, deltaCost, zf > 0.5d? 1.0d -zf : 0d);
                        }
                    }
                    case NORTH_EAST -> {//(1,0), z=x-0.5
                        if (yb < 0.125d) {
                            BlockState state1 = this.getWorld().getBlockState(this.getBlockPos().north());
                            if (AbstractRailBlock.isRail(state1)
                                    && state1.get(((AbstractRailBlock) state1.getBlock()).getShapeProperty()).equals(RailShape.SOUTH_WEST)) {
                                BlockState state2 = this.getWorld().getBlockState(this.getBlockPos().east());
                                if (AbstractRailBlock.isRail(state2)
                                        && state2.get(((AbstractRailBlock) state2.getBlock()).getShapeProperty()).equals(RailShape.SOUTH_WEST)) {
                                    vx = vxNESW;
                                    rotateX = vx > 0d? SQRT1D2 : -SQRT1D2;
                                    rotateZ = rotateX;
                                    xf = MathHelper.clamp(vx *deltaMax +xb, 0.495d, 1.005d);
                                    deltaCost = (xf -xb) /vx;
                                    yf = vy *deltaCost +yb;
                                    if (yf < 0.0625d) {
                                        yf = 0.0625d;
                                        yFixes = YFixes.FLAT;
                                    } else if (yf > 1.005d) {
                                        yf = 1.005d;
                                        deltaCost = (yf -yb)/vy;
                                        xf = vx *deltaCost +xb;
                                    }
                                    zf = xf - 0.5d;
                                    return new MoveData(new Vec3d(xf -xb, yf -yb, zf -zb), rotateX, rotateZ, lineV, yFixes, deltaCost, 0d);
                                }
                            }
                            yf = vy *deltaMax +yb;
                            deltaCost = deltaMax;
                            if (yf < 0.0625d) {
                                yf = 0.0625d;
                                yFixes = YFixes.FLAT;
                            }else if (yf > 1.005d) {
                                yf = 1.005d;
                                deltaCost = (yf -yb)/vy;
                            }
                            HorizontalVector vector = getClockWiseNE(xb, zb);
                            double theta = MathHelper.atan2(vector.x(), -vector.z());
                            double w = (vector.x() *vx + vector.z() *vz > 0) ? -lineV *2d : lineV *2d;
                            double theta1 = theta + w*deltaCost;
                            boolean bl = false;
                            if (theta1 > Math.PI +0.01d) {
                                theta1 = Math.PI +0.01d;
                                xf = 0.5d;
                                zf = -0.005d;
                                rotateX = 0d;
                                rotateZ = -1d;
                                bl = true;
                            } else if (theta1 < 0.5d *Math.PI -0.01d) {
                                theta1 = 0.5d *Math.PI -0.01d;
                                xf = 1.005d;
                                zf = 0.5d;
                                rotateX = 1d;
                                rotateZ = 0d;
                                bl = true;
                            }else {
                                xf = MathHelper.cos((float) theta1);
                                zf = MathHelper.sin((float) theta1);
                                if (w > 0d) {
                                    rotateX = -zf;
                                    rotateZ = xf;
                                } else {
                                    rotateX = zf;
                                    rotateZ = -xf;
                                }
                                xf = xf *0.5d +1d;
                                zf *= 0.5d;
                            }
                            if (bl){
                                deltaCost = (theta1 -theta) /w;
                                vy = vyb;
                                yf = vy *deltaCost +yb;
                                if (yf < 0.0625d) {
                                    yf = 0.0625d;
                                }else {
                                    yFixes = YFixes.KEEP;
                                }
                            }
                            return new MoveData(new Vec3d(xf -xb, yf -yb, zf -zb), rotateX, rotateZ, lineV, yFixes, deltaCost, 0d);
                        }
                    }
                    case NORTH_WEST -> {//(0,0), x+z=0.5
                        if (yb < 0.125d) {
                            BlockState state1 = this.getWorld().getBlockState(this.getBlockPos().north());
                            if (AbstractRailBlock.isRail(state1)
                                    && state1.get(((AbstractRailBlock) state1.getBlock()).getShapeProperty()).equals(RailShape.SOUTH_EAST)) {
                                BlockState state2 = this.getWorld().getBlockState(this.getBlockPos().west());
                                if (AbstractRailBlock.isRail(state2)
                                        && state2.get(((AbstractRailBlock) state2.getBlock()).getShapeProperty()).equals(RailShape.SOUTH_EAST)) {
                                    vx = vxNWSE;
                                    rotateX = vx > 0d? SQRT1D2 : -SQRT1D2;
                                    rotateZ = -rotateX;
                                    xf = MathHelper.clamp(vx *deltaMax +xb, -0.005d, 0.505d);
                                    deltaCost = (xf -xb) /vx;
                                    yf = vy *deltaCost +yb;
                                    if (yf < 0.0625d) {
                                        yf = 0.0625d;
                                        yFixes = YFixes.FLAT;
                                    } else if (yf > 1.005d) {
                                        yf = 1.005d;
                                        deltaCost = (yf -yb)/vy;
                                        xf = vx *deltaCost +xb;
                                    }
                                    zf = 0.5d -xf;
                                    return new MoveData(new Vec3d(xf -xb, yf -yb, zf -zb), rotateX, rotateZ, lineV, yFixes, deltaCost, 0d);
                                }
                            }
                            yf = vy *deltaMax +yb;
                            deltaCost = deltaMax;
                            if (yf < 0.0625d) {
                                yf = 0.0625d;
                                yFixes = YFixes.FLAT;
                            }else if (yf > 1.005d) {
                                yf = 1.005d;
                                deltaCost = (yf -yb)/vy;
                            }
                            HorizontalVector vector = getClockWiseNW(xb, zb);
                            double theta = MathHelper.atan2(vector.x(), -vector.z());
                            double w = (vector.x() *vx + vector.z() *vz > 0) ? -lineV *2d : lineV *2d;
                            double theta1 = theta + w*deltaCost;
                            boolean bl = false;
                            if (theta1 > 0.5d *Math.PI +0.01d) {
                                theta1 = 0.5d *Math.PI +0.01d;
                                xf = -0.005d;
                                zf = 0.5d;
                                rotateX = -1d;
                                rotateZ = 0d;
                                bl = true;
                            } else if (theta1 < -0.01d) {
                                theta1 = -0.01d;
                                xf = 0.5d;
                                zf = -0.005d;
                                rotateX = 0d;
                                rotateZ = -1d;
                                bl = true;
                            }else {
                                xf = MathHelper.cos((float) theta1);
                                zf = MathHelper.sin((float) theta1);
                                if (w > 0d) {
                                    rotateX = -zf;
                                    rotateZ = xf;
                                } else {
                                    rotateX = zf;
                                    rotateZ = -xf;
                                }
                                xf *= 0.5d;
                                zf *= 0.5d;
                            }
                            if (bl){
                                deltaCost = (theta1 -theta) /w;
                                vy = vyb;
                                yf = vy *deltaCost +yb;
                                if (yf < 0.0625d) {
                                    yf = 0.0625d;
                                }else {
                                    yFixes = YFixes.KEEP;
                                }
                            }
                            return new MoveData(new Vec3d(xf -xb, yf -yb, zf -zb), rotateX, rotateZ, lineV, yFixes, deltaCost, 0d);
                        }
                    }
                    case SOUTH_EAST -> {//(1,1), x+z=1.5
                        if (yb < 0.125d) {
                            BlockState state1 = this.getWorld().getBlockState(this.getBlockPos().south());
                            if (AbstractRailBlock.isRail(state1)
                                    && state1.get(((AbstractRailBlock) state1.getBlock()).getShapeProperty()).equals(RailShape.NORTH_WEST)) {
                                BlockState state2 = this.getWorld().getBlockState(this.getBlockPos().east());
                                if (AbstractRailBlock.isRail(state2)
                                        && state2.get(((AbstractRailBlock) state2.getBlock()).getShapeProperty()).equals(RailShape.NORTH_WEST)) {
                                    vx = vxNWSE;
                                    rotateX = vx > 0d? SQRT1D2 : -SQRT1D2;
                                    rotateZ = -rotateX;
                                    xf = MathHelper.clamp(vx *deltaMax +xb, 0.495d, 1.005d);
                                    deltaCost = (xf -xb) /vx;
                                    yf = vy *deltaCost +yb;
                                    if (yf < 0.0625d) {
                                        yf = 0.0625d;
                                        yFixes = YFixes.FLAT;
                                    } else if (yf > 1.005d) {
                                        yf = 1.005d;
                                        deltaCost = (yf -yb)/vy;
                                        xf = vx *deltaCost +xb;
                                    }
                                    zf = 1.5d -xf;
                                    return new MoveData(new Vec3d(xf -xb, yf -yb, zf -zb), rotateX, rotateZ, lineV, yFixes, deltaCost, 0d);
                                }
                            }
                            yf = vy *deltaMax +yb;
                            deltaCost = deltaMax;
                            if (yf < 0.0625d) {
                                yf = 0.0625d;
                                yFixes = YFixes.FLAT;
                            }else if (yf > 1.005d) {
                                yf = 1.005d;
                                deltaCost = (yf -yb)/vy;
                            }
                            HorizontalVector vector = getClockWiseSE(xb, zb);
                            double theta = MathHelper.atan2(vector.x(), -vector.z());
                            double w = (vector.x() *vx + vector.z() *vz > 0) ? -lineV *2d : lineV *2d;
                            double theta1 = theta + w*deltaCost;
                            boolean bl = false;
                            if (theta1 > -0.5d *Math.PI +0.01d) {
                                theta1 = -0.5d *Math.PI +0.01d;
                                xf = 1.005d;
                                zf = 0.5d;
                                rotateX = 1d;
                                rotateZ = 0d;
                                bl = true;
                            } else if (theta1 < -Math.PI -0.01d) {
                                theta1 = -Math.PI -0.01d;
                                xf = 0.5d;
                                zf = 1.005d;
                                rotateX = 0d;
                                rotateZ = 1d;
                                bl = true;
                            }else {
                                xf = MathHelper.cos((float) theta1);
                                zf = MathHelper.sin((float) theta1);
                                if (w > 0d) {
                                    rotateX = -zf;
                                    rotateZ = xf;
                                } else {
                                    rotateX = zf;
                                    rotateZ = -xf;
                                }
                                xf = xf *0.5d +1d;
                                zf = zf *0.5d +1d;
                            }
                            if (bl){
                                deltaCost = (theta1 -theta) /w;
                                vy = vyb;
                                yf = vy *deltaCost +yb;
                                if (yf < 0.0625d) {
                                    yf = 0.0625d;
                                }else {
                                    yFixes = YFixes.KEEP;
                                }
                            }
                            return new MoveData(new Vec3d(xf -xb, yf -yb, zf -zb), rotateX, rotateZ, lineV, yFixes, deltaCost, 0d);
                        }
                    }
                    case SOUTH_WEST -> {//(0,1), z=x+0.5
                        if (yb < 0.125d) {
                            BlockState state1 = this.getWorld().getBlockState(this.getBlockPos().south());
                            if (AbstractRailBlock.isRail(state1)
                                    && state1.get(((AbstractRailBlock) state1.getBlock()).getShapeProperty()).equals(RailShape.NORTH_EAST)) {
                                BlockState state2 = this.getWorld().getBlockState(this.getBlockPos().west());
                                if (AbstractRailBlock.isRail(state2)
                                        && state2.get(((AbstractRailBlock) state2.getBlock()).getShapeProperty()).equals(RailShape.NORTH_EAST)) {
                                    vx = vxNESW;
                                    rotateX = vx > 0d? SQRT1D2 : -SQRT1D2;
                                    rotateZ = rotateX;
                                    xf = MathHelper.clamp(vx *deltaMax +xb, -0.005d, 0.505d);
                                    deltaCost = (xf -xb) /vx;
                                    yf = vy *deltaCost +yb;
                                    if (yf < 0.0625d) {
                                        yf = 0.0625d;
                                        yFixes = YFixes.FLAT;
                                    } else if (yf > 1.005d) {
                                        yf = 1.005d;
                                        deltaCost = (yf -yb)/vy;
                                        xf = vx *deltaCost +xb;
                                    }
                                    zf = xf +0.5d;
                                    return new MoveData(new Vec3d(xf -xb, yf -yb, zf -zb), rotateX, rotateZ, lineV, yFixes, deltaCost, 0d);
                                }
                            }
                            yf = vy *deltaMax +yb;
                            deltaCost = deltaMax;
                            if (yf < 0.0625d) {
                                yf = 0.0625d;
                                yFixes = YFixes.FLAT;
                            }else if (yf > 1.005d) {
                                yf = 1.005d;
                                deltaCost = (yf -yb)/vy;
                            }
                            HorizontalVector vector = getClockWiseSW(xb, zb);
                            double theta = MathHelper.atan2(vector.x(), -vector.z());
                            double w = (vector.x() *vx + vector.z() *vz > 0) ? -lineV *2d : lineV *2d;
                            double theta1 = theta + w*deltaCost;
                            boolean bl = false;
                            if (theta1 > 0.01d) {
                                theta1 = 0.01d;
                                xf = 0.5d;
                                zf = 1.005d;
                                rotateX = 0d;
                                rotateZ = 1d;
                                bl = true;
                            } else if (theta1 < -0.5d *Math.PI -0.01d) {
                                theta1 = -0.5d *Math.PI -0.01d;
                                xf = -0.005d;
                                zf = 0.5d;
                                rotateX = -1d;
                                rotateZ = 0d;
                                bl = true;
                            }else {
                                xf = MathHelper.cos((float) theta1);
                                zf = MathHelper.sin((float) theta1);
                                if (w > 0d) {
                                    rotateX = -zf;
                                    rotateZ = xf;
                                } else {
                                    rotateX = zf;
                                    rotateZ = -xf;
                                }
                                xf *= 0.5d;
                                zf = zf *0.5d +1d;
                            }
                            if (bl){
                                deltaCost = (theta1 -theta) /w;
                                vy = vyb;
                                yf = vy *deltaCost +yb;
                                if (yf < 0.0625d) {
                                    yf = 0.0625d;
                                }else {
                                    yFixes = YFixes.KEEP;
                                }
                            }
                            return new MoveData(new Vec3d(xf -xb, yf -yb, zf -zb), rotateX, rotateZ, lineV, yFixes, deltaCost, 0d);
                        }
                    }
                    case null, default -> {}
                }
            }else {
                if (AbstractRailBlock.isRail(stateDown)){
                    railShape = stateDown.get(((AbstractRailBlock)stateDown.getBlock()).getShapeProperty());
                    switch (railShape){
                        case ASCENDING_EAST -> {
                            if (yb < xb -0.875d) {
                                if (blx) {
                                    xf = xb;
                                    rotateX = -1d;
                                    lineV *= 0.5d;
                                    deltaCost = deltaMax;
                                } else {
                                    xf = MathHelper.clamp(vx *deltaMax +xb, -0.05d, 1.05d);
                                    rotateX = vx > 0d? 1d : -1d;
                                    deltaCost = (xf -xb) /vx;
                                }
                                yf = vy *deltaCost +yb;
                                if (yf < xf -0.9375d) {
                                    yf = xf -0.9375d;
                                    yFixes = rotateX < 0d? YFixes.DOWN : YFixes.UP;
                                } else if (yf > 1.005d) {
                                    yf = 1.005d;
                                    deltaCost = (yf -yb)/vy;
                                    xf = vx *deltaCost +xb;
                                }
                                zf = 0.5d;
                                rotateZ = 0d;
                                return new MoveData(new Vec3d(xf -xb, yf -yb, zf -zb), rotateX, rotateZ, lineV, yFixes, deltaCost, 0.0625d);
                            }
                        }
                        case ASCENDING_WEST -> {
                            if (yb < 0.125d -xb) {
                                if (blx) {
                                    xf = xb;
                                    rotateX = 1d;
                                    lineV *= 0.5d;
                                    deltaCost = deltaMax;
                                } else {
                                    xf = MathHelper.clamp(vx *deltaMax +xb, -0.05d, 1.05d);
                                    rotateX = vx > 0d? 1d : -1d;
                                    deltaCost = (xf -xb) /vx;
                                }
                                yf = vy *deltaCost +yb;
                                if (yf < 0.0625d -xf) {
                                    yf = 0.0625d -xf;
                                    yFixes = rotateX > 0d? YFixes.DOWN : YFixes.UP;
                                } else if (yf > 1.005d) {
                                    yf = 1.005d;
                                    deltaCost = (yf -yb)/vy;
                                    xf = vx *deltaCost +xb;
                                }
                                zf = 0.5d;
                                rotateZ = 0d;
                                return new MoveData(new Vec3d(xf -xb, yf -yb, zf -zb), rotateX, rotateZ, lineV, yFixes, deltaCost, 0.0625d);
                            }
                        }
                        case ASCENDING_NORTH -> {
                            if (yb < 0.125d -zb) {
                                if (blz) {
                                    zf = zb;
                                    rotateZ = 1d;
                                    lineV *= 0.5d;
                                    deltaCost = deltaMax;
                                } else {
                                    zf = MathHelper.clamp(vz *deltaMax +zb, -0.05d, 1.05d);
                                    rotateZ = vz > 0d? 1d : -1d;
                                    deltaCost = (zf -zb) /vz;
                                }
                                yf = vy *deltaCost +yb;
                                if (yf < 0.0625d -zf) {
                                    yf = 0.0625d -zf;
                                    yFixes = rotateZ > 0d? YFixes.DOWN : YFixes.UP;
                                } else if (yf > 1.005d) {
                                    yf = 1.005d;
                                    deltaCost = (yf -yb)/vy;
                                    zf = vz *deltaCost +zb;
                                }
                                xf = 0.5d;
                                rotateX = 0d;
                                return new MoveData(new Vec3d(xf -xb, yf -yb, zf -zb), rotateX, rotateZ, lineV, yFixes, deltaCost, 0.0625d);
                            }
                        }
                        case ASCENDING_SOUTH -> {
                            if (yb < zb -0.875d) {
                                if (blz) {
                                    zf = zb;
                                    rotateZ = -1d;
                                    lineV *= 0.5d;
                                    deltaCost = deltaMax;
                                } else {
                                    zf = MathHelper.clamp(vz *deltaMax +zb, -0.05d, 1.05d);
                                    rotateZ = vz > 0d? 1d : -1d;
                                    deltaCost = (zf -zb) /vz;
                                }
                                yf = vy *deltaCost +yb;
                                if (yf < zf -0.9375d) {
                                    yf = zf -0.9375d;
                                    yFixes = rotateZ < 0d? YFixes.DOWN : YFixes.UP;
                                } else if (yf > 1.005d) {
                                    yf = 1.005d;
                                    deltaCost = (yf -yb)/vy;
                                    zf = vz *deltaCost +zb;
                                }
                                xf = 0.5d;
                                rotateX = 0d;
                                return new MoveData(new Vec3d(xf -xb, yf -yb, zf -zb), rotateX, rotateZ, lineV, yFixes, deltaCost, 0.0625d);
                            }
                        }
                        case null, default -> {}
                    }
                }
            }
        }
        xf = MathHelper.clamp(vx *deltaMax +xb, -0.005d, 1.005d);
        yf = MathHelper.clamp(vy *deltaMax +yb, -0.005d, 1.005d);
        zf = MathHelper.clamp(vz *deltaMax +zb, -0.005d, 1.005d);
        deltaCost = Math.min(blx? deltaMax : (xf -xb)/vx, Math.min(bly? deltaMax : (yf -yb)/vy, blz? deltaMax : (zf -zb)/vz));
        xf = vx *deltaCost +xb;
        yf = vy *deltaCost +yb;
        zf = vz *deltaCost +zb;
        double compressBoxY = 0d;
        if (this.canOnRail && deltaCost > 1.0E-3) {
            if (AbstractRailBlock.isRail(state)) {
                railShape = state.get(((AbstractRailBlock) state.getBlock()).getShapeProperty());
                double txf;
                double tyf;
                double tzf;
                double tdc;
                switch (railShape) {
                    case EAST_WEST, NORTH_SOUTH, NORTH_EAST, NORTH_WEST, SOUTH_EAST, SOUTH_WEST -> {
                        if (!bly) {
                            tyf = 0.0625d;
                            tdc = (tyf -yb) /vy;
                            if (tdc > 0 && tdc < deltaCost) {
                                txf = blx? xb : vx *tdc +xb;
                                tzf = blz? zb : vz *tdc +zb;
                                if (checkPosOnRail(state, stateDown, new Vec3d(txf, tyf, tzf))) {
                                    return new MoveData(new Vec3d(txf -xb, tyf -yb, tzf -zb), rotateX, rotateZ, lineV, yFixes, tdc, 0d);
                                }
                            }
                        }
                    }
                    case ASCENDING_EAST -> {
                        compressBoxY = xf > 0.5d? 1.0d -xf : 0d;
                        if (!blx || !bly) {
                            Optional<NotSoPrecisedPlaneCalculator.Dot> dot = AP.intersection(NotSoPrecisedPlaneCalculator.Segment.from(xb, yb, xf, yf));
                            if (dot.isPresent()) {
                                txf = dot.get().x();
                                tyf = dot.get().y();
                                if (blx) {
                                    tdc = (tyf -yb) /vy;
                                } else {
                                    tdc = (txf -xb) /vx;
                                }
                                tzf = blz? zb : vz *tdc +zb;
                                if (checkPosOnRail(state, stateDown, new Vec3d(txf, tyf, tzf))) {
                                    return new MoveData(new Vec3d(txf -xb, tyf -yb, tzf -zb), rotateX, rotateZ, lineV, yFixes, tdc, compressBoxY);
                                }
                            }
                        }
                    }
                    case ASCENDING_WEST -> {
                        compressBoxY = xf < 0.5d? xf : 0d;
                        if (!blx || !bly) {
                            Optional<NotSoPrecisedPlaneCalculator.Dot> dot = AN.intersection(NotSoPrecisedPlaneCalculator.Segment.from(xb, yb, xf, yf));
                            if (dot.isPresent()) {
                                txf = dot.get().x();
                                tyf = dot.get().y();
                                if (blx) {
                                    tdc = (tyf -yb) /vy;
                                } else {
                                    tdc = (txf -xb) /vx;
                                }
                                tzf = blz? zb : vz *tdc +zb;
                                if (checkPosOnRail(state, stateDown, new Vec3d(txf, tyf, tzf))) {
                                    return new MoveData(new Vec3d(txf -xb, tyf -yb, tzf -zb), rotateX, rotateZ, lineV, yFixes, tdc, compressBoxY);
                                }
                            }
                        }
                    }
                    case ASCENDING_NORTH -> {
                        compressBoxY = zf < 0.5d? zf : 0d;
                        if (!blz || !bly) {
                            Optional<NotSoPrecisedPlaneCalculator.Dot> dot = AN.intersection(NotSoPrecisedPlaneCalculator.Segment.from(zb, yb, zf, yf));
                            if (dot.isPresent()) {
                                tzf = dot.get().x();
                                tyf = dot.get().y();
                                if (blz) {
                                    tdc = (tyf -yb) /vy;
                                } else {
                                    tdc = (tzf -xb) /vz;
                                }
                                txf = blx? xb : vx *tdc +xb;
                                if (checkPosOnRail(state, stateDown, new Vec3d(txf, tyf, tzf))) {
                                    return new MoveData(new Vec3d(txf -xb, tyf -yb, tzf -zb), rotateX, rotateZ, lineV, yFixes, tdc, compressBoxY);
                                }
                            }
                        }
                    }
                    case ASCENDING_SOUTH -> {
                        compressBoxY = zf > 0.5d? 1.0d -zf : 0d;
                        if (!blz || !bly) {
                            Optional<NotSoPrecisedPlaneCalculator.Dot> dot = AP.intersection(NotSoPrecisedPlaneCalculator.Segment.from(zb, yb, zf, yf));
                            if (dot.isPresent()) {
                                tzf = dot.get().x();
                                tyf = dot.get().y();
                                if (blz) {
                                    tdc = (tyf -yb) /vy;
                                } else {
                                    tdc = (tzf -xb) /vz;
                                }
                                txf = blx? xb : vx *tdc +xb;
                                if (checkPosOnRail(state, stateDown, new Vec3d(txf, tyf, tzf))) {
                                    return new MoveData(new Vec3d(txf -xb, tyf -yb, tzf -zb), rotateX, rotateZ, lineV, yFixes, tdc, compressBoxY);
                                }
                            }
                        }
                    }
                    case null, default -> {}
                }
            } else {
                if (AbstractRailBlock.isRail(stateDown)) {
                    railShape = stateDown.get(((AbstractRailBlock) stateDown.getBlock()).getShapeProperty());
                    double txf;
                    double tyf;
                    double tzf;
                    double tdc;
                    switch (railShape) {
                        case ASCENDING_EAST -> {
                            compressBoxY = 0.0625d;
                            if (!blx || !bly) {
                                Optional<NotSoPrecisedPlaneCalculator.Dot> dot = APD.intersection(NotSoPrecisedPlaneCalculator.Segment.from(xb, yb, xf, yf));
                                if (dot.isPresent()) {
                                    txf = dot.get().x();
                                    tyf = dot.get().y();
                                    if (blx) {
                                        tdc = (tyf -yb) /vy;
                                    } else {
                                        tdc = (txf -xb) /vx;
                                    }
                                    tzf = blz? zb : vz *tdc +zb;
                                    if (checkPosOnRail(state, stateDown, new Vec3d(txf, tyf, tzf))) {
                                        return new MoveData(new Vec3d(txf -xb, tyf -yb, tzf -zb), rotateX, rotateZ, lineV, yFixes, tdc, compressBoxY);
                                    }
                                }
                            }
                        }
                        case ASCENDING_WEST -> {
                            compressBoxY = 0.0625d;
                            if (!blx || !bly) {
                                Optional<NotSoPrecisedPlaneCalculator.Dot> dot = AND.intersection(NotSoPrecisedPlaneCalculator.Segment.from(xb, yb, xf, yf));
                                if (dot.isPresent()) {
                                    txf = dot.get().x();
                                    tyf = dot.get().y();
                                    if (blx) {
                                        tdc = (tyf -yb) /vy;
                                    } else {
                                        tdc = (txf -xb) /vx;
                                    }
                                    tzf = blz? zb : vz *tdc +zb;
                                    if (checkPosOnRail(state, stateDown, new Vec3d(txf, tyf, tzf))) {
                                        return new MoveData(new Vec3d(txf -xb, tyf -yb, tzf -zb), rotateX, rotateZ, lineV, yFixes, tdc, compressBoxY);
                                    }
                                }
                            }
                        }
                        case ASCENDING_NORTH -> {
                            compressBoxY = 0.0625d;
                            if (!blz || !bly) {
                                Optional<NotSoPrecisedPlaneCalculator.Dot> dot = AND.intersection(NotSoPrecisedPlaneCalculator.Segment.from(zb, yb, zf, yf));
                                if (dot.isPresent()) {
                                    tzf = dot.get().x();
                                    tyf = dot.get().y();
                                    if (blz) {
                                        tdc = (tyf -yb) /vy;
                                    } else {
                                        tdc = (tzf -xb) /vz;
                                    }
                                    txf = blx? xb : vx *tdc +xb;
                                    if (checkPosOnRail(state, stateDown, new Vec3d(txf, tyf, tzf))) {
                                        return new MoveData(new Vec3d(txf -xb, tyf -yb, tzf -zb), rotateX, rotateZ, lineV, yFixes, tdc, compressBoxY);
                                    }
                                }
                            }
                        }
                        case ASCENDING_SOUTH -> {
                            compressBoxY = 0.0625d;
                            if (!blz || !bly) {
                                Optional<NotSoPrecisedPlaneCalculator.Dot> dot = APD.intersection(NotSoPrecisedPlaneCalculator.Segment.from(zb, yb, zf, yf));
                                if (dot.isPresent()) {
                                    tzf = dot.get().x();
                                    tyf = dot.get().y();
                                    if (blz) {
                                        tdc = (tyf -yb) /vy;
                                    } else {
                                        tdc = (tzf -xb) /vz;
                                    }
                                    txf = blx? xb : vx *tdc +xb;
                                    if (checkPosOnRail(state, stateDown, new Vec3d(txf, tyf, tzf))) {
                                        return new MoveData(new Vec3d(txf -xb, tyf -yb, tzf -zb), rotateX, rotateZ, lineV, yFixes, tdc, compressBoxY);
                                    }
                                }
                            }
                        }
                        case null, default -> {
                        }
                    }
                }
            }
        }
        return new MoveData(new Vec3d(xf -xb, yf -yb, zf -zb), rotateX, rotateZ, lineV, yFixes, deltaCost, compressBoxY);
    }

    @Unique
    private MoveData moveYOnly(BlockState state, BlockState stateDown, double xb, double yb, double zb,double rotateX, double rotateZ, double vy, double deltaMax){
        double yf = MathHelper.clamp(vy *deltaMax +yb, -0.005d, 1.005d);
        double compressBoxY = 0d;
        if (AbstractRailBlock.isRail(state)) {
            RailShape railShape = state.get(((AbstractRailBlock) state.getBlock()).getShapeProperty());
            switch (railShape) {
                case EAST_WEST, NORTH_SOUTH, NORTH_EAST, NORTH_WEST, SOUTH_EAST, SOUTH_WEST -> {
                    if (yf < 0.0625d) {
                        return new MoveData(new Vec3d(0d, 0.0625d -yb, 0d), rotateX, rotateZ, 0d, YFixes.FLAT, deltaMax, 0d);
                    }
                }
                case ASCENDING_EAST -> {
                    compressBoxY = xb > 0.5d? 1.0d -xb : 0d;
                    if (yf < 0.0625d + xb) {
                        return new MoveData(new Vec3d(0d, 0.0625d +xb -yb, 0d), -1d, 0d, 0d, YFixes.DOWN, deltaMax, compressBoxY);
                    }
                }
                case ASCENDING_WEST -> {
                    compressBoxY = xb < 0.5d? xb : 0d;
                    if (yf < 1.0625d - xb) {
                        return new MoveData(new Vec3d(0d, 1.0625d -xb -yb, 0d), 1d, 0d, 0d, YFixes.DOWN, deltaMax, compressBoxY);
                    }
                }
                case ASCENDING_NORTH -> {
                    compressBoxY = zb < 0.5d? zb : 0d;
                    if (yf < 1.0625d - zb) {
                        return new MoveData(new Vec3d(0d, 1.0625d -zb -yb, 0d), 0d, 1d, 0d, YFixes.DOWN, deltaMax, compressBoxY);
                    }
                }
                case ASCENDING_SOUTH -> {
                    compressBoxY = zb > 0.5d? 1.0d -zb : 0d;
                    if (yf < 0.0625d + zb) {
                        return new MoveData(new Vec3d(0d, 0.0625d +zb -yb, 0d), 0d, -1d, 0d, YFixes.DOWN, deltaMax, compressBoxY);
                    }
                }
                case null, default -> {
                }
            }
        } else if (AbstractRailBlock.isRail(stateDown)) {
            RailShape railShape = stateDown.get(((AbstractRailBlock) stateDown.getBlock()).getShapeProperty());
            switch (railShape){
                case ASCENDING_EAST -> {
                    compressBoxY = 0.0625d;
                    if (yf < xb -0.9375d) {
                        return new MoveData(new Vec3d(0d, xb -0.9375d -yb, 0d), -1d, 0d, 0d, YFixes.DOWN, deltaMax, 0d);
                    }
                }
                case ASCENDING_WEST -> {
                    compressBoxY = 0.0625d;
                    if (yf < 0.0625d - xb) {
                        return new MoveData(new Vec3d(0d, 0.0625d - xb -yb, 0d), 1d, 0d, 0d, YFixes.DOWN, deltaMax, 0d);
                    }
                }
                case ASCENDING_NORTH -> {
                    compressBoxY = 0.0625d;
                    if (yf < 0.0625d - zb) {
                        return new MoveData(new Vec3d(0d, 0.0625d - zb -yb, 0d), 0d, 1d, 0d, YFixes.DOWN, deltaMax, 0d);
                    }
                }
                case ASCENDING_SOUTH -> {
                    compressBoxY = 0.0625d;
                    if (yf < zb -0.9375d) {
                        return new MoveData(new Vec3d(0d, zb -0.9375d -yb, 0d), 0d, -1d, 0d, YFixes.DOWN, deltaMax, 0d);
                    }
                }
                case null, default -> {
                }
            }
        }
        return new MoveData(new Vec3d(0d, yf -yb, 0d), rotateX, rotateZ, 0d, YFixes.KEEP, (yf -yb)/vy, compressBoxY);
    }

    @Unique
    private HorizontalVector getClockWiseNE(double px, double pz){
        px = Math.min(px -1d, -1.0E-4);
        pz = Math.max(pz, 1.0E-4);
        double l = Math.sqrt(px*px + pz*pz);
        return new HorizontalVector(pz/l, -px/l);
    }

    @Unique
    private HorizontalVector getClockWiseNW(double px, double pz){
        px = Math.max(px, 1.0E-4);
        pz = Math.max(pz, 1.0E-4);
        double l = Math.sqrt(px*px + pz*pz);
        return new HorizontalVector(pz/l, -px/l);
    }

    @Unique
    private HorizontalVector getClockWiseSW(double px, double pz){
        px = Math.max(px, 1.0E-4);
        pz = Math.min(pz -1d, -1.0E-4);
        double l = Math.sqrt(px*px + pz*pz);
        return new HorizontalVector(pz/l, -px/l);
    }

    @Unique
    private HorizontalVector getClockWiseSE(double px, double pz){
        px = Math.min(px -1d, -1.0E-4);
        pz = Math.min(pz -1d, -1.0E-4);
        double l = Math.sqrt(px*px + pz*pz);
        return new HorizontalVector(pz/l, -px/l);
    }

    @Override
    protected boolean updateWaterState() {
        return this.isTouchingWater() || this.isInLava();
    }

    @Inject(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/vehicle/AbstractMinecartEntity;applyGravity()V"), cancellable = true)
    private void tickTrain(CallbackInfo ci){
        if (this.getWorld().isClient){
            return;
        }
        if (this.train == null){
            BlockState state = this.getWorld().getBlockState(this.getBlockPos());
            Vec3d v = this.getVelocity();
            this.lineV = v.horizontalLength();
            if (this.lineV > 1.0E-4){
                this.rotateX = v.x/this.lineV;
                this.rotateZ = v.z/this.lineV;
            }else {
                if (AbstractRailBlock.isRail(state) && this.canOnRail) {
                    RailShape railShape = state.get(((AbstractRailBlock) state.getBlock()).getShapeProperty());
                    switch (railShape) {
                        case EAST_WEST -> {
                            this.rotateX = v.x < 0d ? -1d : 1d;
                            this.rotateZ = 0d;
                        }
                        case NORTH_SOUTH -> {
                            this.rotateX = 0d;
                            this.rotateZ = v.z < 0d ? -1d : 1d;
                        }
                        case NORTH_EAST -> {
                            HorizontalVector vec = getClockWiseNE(this.getX() -Math.floor(this.getX()), this.getZ() -Math.floor(this.getZ()));
                            this.rotateX = vec.x();
                            this.rotateZ = vec.z();
                        }
                        case SOUTH_WEST -> {
                            HorizontalVector vec = getClockWiseSW(this.getX() -Math.floor(this.getX()), this.getZ() -Math.floor(this.getZ()));
                            this.rotateX = vec.x();
                            this.rotateZ = vec.z();
                        }
                        case NORTH_WEST -> {
                            HorizontalVector vec = getClockWiseNW(this.getX() -Math.floor(this.getX()), this.getZ() -Math.floor(this.getZ()));
                            this.rotateX = vec.x();
                            this.rotateZ = vec.z();
                        }
                        case SOUTH_EAST -> {
                            HorizontalVector vec = getClockWiseSE(this.getX() -Math.floor(this.getX()), this.getZ() -Math.floor(this.getZ()));
                            this.rotateX = vec.x();
                            this.rotateZ = vec.z();
                        }
                        case ASCENDING_EAST -> {
                            this.rotateX = -1d;
                            this.rotateZ = 0d;
                        }
                        case ASCENDING_WEST -> {
                            this.rotateX = 1d;
                            this.rotateZ = 0d;
                        }
                        case ASCENDING_NORTH -> {
                            this.rotateX = 0d;
                            this.rotateZ = 1d;
                        }
                        case ASCENDING_SOUTH -> {
                            this.rotateX = 0d;
                            this.rotateZ = -1d;
                        }
                    }
                }
            }
        }else {
            this.train.tick(true);
            this.firstUpdate = false;
            if (this.beforeOnRail > 0){
                --this.beforeOnRail;
            }else {
                this.canOnRail = true;
            }
            ci.cancel();
        }
    }

    @Redirect(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/AbstractRailBlock;isRail(Lnet/minecraft/block/BlockState;)Z"))
    private boolean checkRail(BlockState state){
        return this.canOnRail && AbstractRailBlock.isRail(state);
    }

    @Inject(method = "getVelocityMultiplier", at = @At("HEAD"), cancellable = true)
    private void applyTrain(CallbackInfoReturnable<Float> cir){
        if (this.train != null){
            cir.setReturnValue(1.0f);
        }
    }

    @Inject(method = "pushAwayFrom", at = @At("HEAD"), cancellable = true)
    private void newPushRule(Entity entity, CallbackInfo ci){
        ci.cancel();
        if (!(this.getWorld().isClient || this.noClip || entity.noClip) && !this.isConnectedThroughVehicle(entity)){
            double d = entity.getX() - this.getX();
            double e = entity.getZ() - this.getZ();
            double f = d * d + e * e;
            if (f >= 1.0E-4) {
                f = Math.sqrt(f);
                d /= f;
                e /= f;
                double g = 0.05d / f;
                if (g > 0.05d) {
                    g = 0.05d;
                }
                d *= g;
                e *= g;
                entity.addVelocity(d /4.0d, 0.0, e /4.0d);
                if (this.train == null){
                    this.addVelocity(-d, 0.0, -e);
                }
            }
        }
    }

    @Inject(method = "readCustomDataFromNbt", at = @At("RETURN"))
    private void readTrain(NbtCompound nbt, CallbackInfo ci){
        this.train = MinecartTrain.loadFromNbt(nbt, asMineCart());
        if (nbt.contains("rotateX")) {
            this.rotateX = nbt.getDouble("rotateX");
            this.rotateZ = nbt.getDouble("rotateZ");
        }else {
            this.railway_accelerator$updateHorizontalVector(this.getVelocity().x, this.getVelocity().z);
        }
        this.lineV = this.getVelocity().horizontalLength();
    }

    @Inject(method = "writeCustomDataToNbt", at = @At("RETURN"))
    private void writeTrain(NbtCompound nbt, CallbackInfo ci){
        MinecartTrain.writeToNbt(nbt, this.train);
        nbt.putDouble("rotateX", this.rotateX);
        nbt.putDouble("rotateZ", this.rotateZ);
    }

    @Override
    public ActionResult interact(PlayerEntity player, Hand hand) {
        ItemStack stack = player.getStackInHand(hand);
        if (this.getWorld() instanceof ServerWorld world && this.onRail && stack.getItem() instanceof MinecartItem minecartItem){
            BlockPos pos = this.getBlockPos();
            BlockState state = world.getBlockState(pos);
            if (AbstractRailBlock.isRail(state)){
                Vec3d newPos;
                double d = this.getWidth() +0.25d;
                switch (state.get(((AbstractRailBlock)state.getBlock()).getShapeProperty())){
                    case EAST_WEST -> newPos = player.getX() > this.getX()? this.getPos().add(d, 0d, 0d) : this.getPos().add(-d, 0d, 0d);
                    case NORTH_SOUTH -> newPos = player.getZ() > this.getZ()? this.getPos().add(0d, 0d, d) : this.getPos().add(0d, 0d, -d);
                    case null, default -> {
                        return ActionResult.PASS;
                    }
                }
                if (!AbstractRailBlock.isRail(world.getBlockState(BlockPos.ofFloored(newPos.x, newPos.y, newPos.z)))){
                    return ActionResult.PASS;
                }
                AbstractMinecartEntity minecart = AbstractMinecartEntity.create(world, newPos.x, newPos.y, newPos.z, minecartItem.type, stack, player);
                minecart.railway_accelerator$updateHorizontalVector(this.getX() -minecart.getX(), this.getZ() -minecart.getZ());
                minecart.railway_accelerator$updateLineVelocity(this.lineV);
                if (!MinecartTrain.link(asMineCart(), minecart)){
                    minecart.discard();
                }else {
                    world.spawnEntity(minecart);
                    world.emitGameEvent(GameEvent.ENTITY_PLACE, pos, GameEvent.Emitter.of(player, world.getBlockState(pos.down())));
                    stack.decrementUnlessCreative(1, player);
                }
                return ActionResult.SUCCESS;
            }
        }
        return ActionResult.PASS;
    }
}
